// cpp/imageCache/LRUCache.cpp
//
// Created on 2024/8/21.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include "LRUCache.h"
#include <list>
#include <map>
#include <mutex>
#include <string>
#include <dirent.h>
#include <sys/stat.h>
#include <hilog/log.h>
#include <fstream>

const unsigned int LOG_PRINT_DOMAIN = 0xff00;
void LRUCache::init(size_t maxSize, const std::string &cacheDir) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "LRUCache", "LRUCache init: %{public}lu, %{public}s", maxSize,
                 cacheDir.c_str());
    this->maxSize = maxSize;
    currentSize = 0;
    restoreFromCacheDir(cacheDir);
}

void LRUCache::restoreFromCacheDir(const std::string &cacheDir) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "LRUCache", "LRUCache restoreFromCacheDir");
    DIR *dir = opendir(cacheDir.c_str());
    if (dir == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "LRUCache", "LRUCache opendir failed %{public}s",
                     cacheDir.c_str());
        return;
    }
    currentSize = 0;
    std::vector<std::pair<time_t, std::string>> fileStamp;
    std::map<std::string, size_t> fileSize;
    struct dirent *entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_REG) {
            std::string filePath = cacheDir + "/" + entry->d_name;
            struct stat fileStat;
            if (stat(filePath.c_str(), &fileStat) == 0) {
                fileStamp.emplace_back(fileStat.st_atime, entry->d_name);
                fileSize[entry->d_name] = fileStat.st_size;
                currentSize += fileStat.st_size;
            } else {
                OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageCache",
                             "LRUCache::restoreFromCacheDir"
                             " Failed to open directory: %{public}s, errno: %{public}d",
                             cacheDir.c_str(), errno);
            }
        }
    }
    closedir(dir);
    // 按照修改事件排序。
    std::sort(fileStamp.begin(), fileStamp.end(), std::greater<>());
    for (const auto &[modTime, key] : fileStamp) {
        put(key, fileSize[key]);
    }

    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageCache",
                 "LRUCache::restoreFromCacheDir"
                 " current size %{public}lu",
                 currentSize);
}

bool LRUCache::get(const std::string &key, size_t &bufferSize) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageCache", "LRUCache %{public}s, %{public}lu", key.c_str(),
                 bufferSize);
    std::lock_guard<std::mutex> lock(cacheMutex);
    auto it = cacheMap.find(key);
    if (it == cacheMap.end()) {
        return false;
    }
    cacheList.splice(cacheList.begin(), cacheList, it->second.second);
    return true;
}

void LRUCache::put(const std::string &key, size_t bufferSize) {
    std::lock_guard<std::mutex> lock(cacheMutex);
    auto it = cacheMap.find(key);
    if (bufferSize > maxSize) {
        return;
    }

    // 命中缓存
    if (it != cacheMap.end()) {
        it->second.first = bufferSize;
        cacheList.splice(cacheList.begin(), cacheList, it->second.second);
        return;
    }

    while (currentSize + bufferSize > maxSize) {
        auto last = cacheList.back();
        // deleteFileKey = last;
        auto filename = cacheDir + "/" + last;
        std::remove(filename.c_str());
        currentSize -= cacheMap[last].first;
        cacheMap.erase(last);
        cacheList.pop_back();
    }

    // 插入新元素
    cacheList.push_front(key);
    cacheMap[key] = {bufferSize, cacheList.begin()};
    currentSize += bufferSize;
}
